package com.pai4j.jianlipai.service.jianlibot;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.pai4j.common.enums.jianlibot.ResumeTemplateEnum;
import com.pai4j.common.util.IDGenerateUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.domain.dto.jianlipai.ResumeDetail;
import com.pai4j.domain.dto.llm.ChatCompletionMessage;
import com.pai4j.domain.vo.request.jianlipai.CreateResumeVO;
import com.pai4j.domain.vo.request.jianlipai.OnePaperSaveRequestVO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.domain.vo.response.UserSafeResponseInfoVO;
import com.pai4j.domain.vo.response.jianlipai.ResumeBaseVO;
import com.pai4j.domain.vo.response.jianlipai.ResumeDetailVO;
import com.pai4j.domain.vo.response.jianlipai.ResumeManageVO;
import com.pai4j.jianlipai.repository.dao.jianlibot.IJianLiPaiResumeDAO;
import com.pai4j.jianlipai.repository.entity.jianlibot.JianLiPaiResumeEntity;
import com.pai4j.jianlipai.service.jianlibot.templates.AbstractTemplateService;
import com.pai4j.jianlipai.service.jianlibot.templates.ResumeTemplateFactory;
import com.pai4j.remote.user.UserServiceClient;
import jakarta.annotation.Resource;
import jakarta.persistence.criteria.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import com.pai4j.common.enums.RedisKeyEnum;


/**
 * 简历业务服务
 *
 * @author: CYM-pai
 * @date: 2025/05/05 15:12
 **/
@Slf4j
@Service
public class JianLiPaiService {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private IJianLiPaiResumeDAO jianLiBotResumeDAO;

    @Resource
    private UserServiceClient userServiceClient;

    public void deleteResume(String userId, String resumeId) {
        JianLiPaiResumeEntity resume = jianLiBotResumeDAO.findAllByResumeIdAndCreateBy(resumeId, userId);
        Assert.isTrue(resume != null, "简历不存在");
        Assert.isTrue(!resume.getDeleted(), "简历已经删除！");
        Assert.isTrue(resume.getCreateBy().equals(userId), "无删除权限！");
        resume.setDeleted(true);
        jianLiBotResumeDAO.save(resume);
        // 删除缓存
        deleteResumeDetailCache(resumeId);
        // 刷新缓存列表
        removeUserResumeListCache(userId, resumeId);
    }

    public String createResume(CreateResumeVO createResumeVO) {
        String resumeId = IDGenerateUtil.uuid();
        JianLiPaiResumeEntity resumeEntity = new JianLiPaiResumeEntity();
        resumeEntity.setResumeId(resumeId);
        resumeEntity.setCover(ResumeTemplateEnum.getCover(createResumeVO.getTemplateCode()));
        resumeEntity.setCreateBy(createResumeVO.getUserId());
        resumeEntity.setCreateDate(new Date());
        resumeEntity.setTemplateCode(createResumeVO.getTemplateCode());
        resumeEntity.setName(createResumeVO.getName());
        resumeEntity.setDeleted(false);
        resumeEntity = jianLiBotResumeDAO.save(resumeEntity);
        // 添加用户最新简历缓存
        addUserResumeListCache(resumeEntity.getCreateBy(), resumeId, resumeEntity.getId());
        // 刷新简历详情缓存
        refreshResumeDetailCache(resumeId);
        return resumeId;
    }

    /**
     * 获取用户简历列表
     *
     */
    public PAIPageResponseBeanUtil<List<ResumeManageVO>> getResumeList(Integer pageNo, Integer pageSize) {
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Direction.DESC, "id"));
        Specification<JianLiPaiResumeEntity> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("deleted").as(Boolean.class), false));
            Predicate[] pre = new Predicate[predicates.size()];
            criteriaQuery.where(predicates.toArray(pre));
            return criteriaQuery.getRestriction();
        });
        Page<JianLiPaiResumeEntity> jianLiBotResumeEntities = jianLiBotResumeDAO.findAll(specification, pageable);
        if (jianLiBotResumeEntities.getTotalElements() == 0 ||
                org.springframework.util.CollectionUtils.isEmpty(jianLiBotResumeEntities.getContent())) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, jianLiBotResumeEntities.getTotalElements(), Collections.emptyList());
        }
        List<JianLiPaiResumeEntity> jianLiBotRechargeRecordEntities1 = jianLiBotResumeEntities.getContent();
        Set<String> userIds = jianLiBotRechargeRecordEntities1.stream().map(JianLiPaiResumeEntity::getCreateBy).collect(Collectors.toSet());
        Map<String, UserBaseResponseInfoVO> userMap = (Map<String, UserBaseResponseInfoVO>) userServiceClient.batchGetMap(userIds);
        List<ResumeManageVO> resumeManageVOS = jianLiBotRechargeRecordEntities1.stream().map(
            resume -> {
                ResumeManageVO resumeManageVO = new ResumeManageVO();
                resumeManageVO.setResumeId(resume.getResumeId());
                resumeManageVO.setName(resume.getName());
                resumeManageVO.setCover(resume.getCover());
                resumeManageVO.setTemplateCode(resume.getTemplateCode());
                if (resume.getUpdateDate() != null) {
                    resumeManageVO.setLastUpdateTime(resume.getUpdateDate().getTime());
                } else {
                    resumeManageVO.setLastUpdateTime(resume.getCreateDate().getTime());
                }
                resumeManageVO.setFileSize(resume.getFileSize());
                UserBaseResponseInfoVO user = userMap.get(resume.getCreateBy());
                if (user == null) {
                    user = new UserSafeResponseInfoVO();
                    user.setUserId(resume.getCreateBy());
                    user.setUserName("未知用户");
                }
                resumeManageVO.setAuthor(user);
                return resumeManageVO;
            }
        ).collect(Collectors.toList());
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, jianLiBotResumeEntities.getTotalElements(), resumeManageVOS);
    }

    public List<ResumeBaseVO> getUserResumeListCache(String userId) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getKey(userId);
        if (!redisUtil.exists(key)) {
            this.refreshUserResumeListCache(userId);
        }
        Set<String> caches = redisUtil.zreverseRangeByScore(key, Long.MAX_VALUE, 0, Integer.MAX_VALUE);
        if (CollectionUtils.isEmpty(caches)) {
            return Collections.emptyList();
        }
        List<ResumeDetailVO> resumeList = batchGetResumeDetailListCache(new ArrayList<>(caches));
        if (CollectionUtils.isEmpty(resumeList)) {
            return Collections.emptyList();
        }
        return resumeList.stream().map(r -> {
            ResumeBaseVO resumeBase = new ResumeBaseVO();
            resumeBase.setName(r.getName());
            resumeBase.setResumeId(r.getResumeId());
            resumeBase.setCover(r.getCover());
            resumeBase.setFileSize(r.getFileSize());
            resumeBase.setUserId(r.getUserId());
            resumeBase.setLastUpdateTime(r.getLastUpdateTime());
            resumeBase.setTemplateCode(r.getTemplateCode());
            return resumeBase;
        }).collect(Collectors.toList());
    }

    private void addUserResumeListCache(String userId, String resumeId, Long id) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getKey(userId);
        if (redisUtil.exists(key)) {
            redisUtil.zadd(key, resumeId, id, RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getExpireTime());
            return;
        }
        this.refreshUserResumeListCache(userId);
    }

    private void removeUserResumeListCache(String userId, String resumeId) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getKey(userId);
        redisUtil.zremove(key, resumeId);
    }

    private void refreshUserResumeListCache(String userId) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getKey(userId);
        List<JianLiPaiResumeEntity> list = jianLiBotResumeDAO.findAllByCreateByAndDeletedOrderByIdDesc(userId, false);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        Set<ZSetOperations.TypedTuple<String>> tuples = list.stream().map(
                resume -> {
                    String value = String.valueOf(resume.getResumeId());
                    Double score = Double.valueOf(resume.getId());
                    return new DefaultTypedTuple<>(value, score);
                }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, RedisKeyEnum.JIANLI_BOT_RESUME_LIST_CACHE.getExpireTime());
    }

    private ResumeDetailVO refreshResumeDetailCache(String resumeId) {
        JianLiPaiResumeEntity resumeEntity = jianLiBotResumeDAO.findAllByResumeId(resumeId);
        if (resumeEntity == null || resumeEntity.getDeleted()) {
            return null;
        }
        ResumeDetailVO resumeDetailVO = new ResumeDetailVO();
        BeanUtils.copyProperties(resumeEntity, resumeDetailVO);
        resumeDetailVO.setUserId(resumeEntity.getCreateBy());
        if (resumeEntity.getUpdateDate() != null) {
            resumeDetailVO.setLastUpdateTime(resumeEntity.getUpdateDate().getTime());
        } else {
            resumeDetailVO.setLastUpdateTime(resumeEntity.getCreateDate().getTime());
        }
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getKey(resumeId);
        redisUtil.set(key, JsonUtil.toJsonString(resumeDetailVO), RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getExpireTime());
        return resumeDetailVO;
    }

    private void deleteResumeDetailCache(String resumeId) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getKey(resumeId);
        redisUtil.remove(key);
    }

    public Map<String, ResumeDetailVO> batchGetResumeDetailMapCache(List<String> resumeIdList) {
        Map<String, ResumeDetailVO> map = new HashMap<>();
        List<String> keys = resumeIdList.stream().map(id ->
                RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getKey(id)).collect(Collectors.toList());
        List<String> caches = redisUtil.mget(keys);
        for (int i = 0; i < resumeIdList.size(); i++) {
            String resumeId = resumeIdList.get(i);
            String cache = caches.get(i);
            ResumeDetailVO resumeDetailVO;
            if (StringUtils.isNotBlank(cache)) {
                resumeDetailVO = JsonUtil.fromJson(cache, ResumeDetailVO.class);
            } else {
                resumeDetailVO = refreshResumeDetailCache(resumeId);
            }
            if (resumeDetailVO != null) {
                map.put(resumeId, resumeDetailVO);
            }
        }
        return map;
    }

    public List<ResumeDetailVO> batchGetResumeDetailListCache(List<String> resumeIdList) {
        List<ResumeDetailVO> list = new ArrayList<>();
        List<String> keys = resumeIdList.stream().map(id ->
                RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getKey(id)).collect(Collectors.toList());
        List<String> caches = redisUtil.mget(keys);
        for (int i = 0; i < resumeIdList.size(); i++) {
            String resumeId = resumeIdList.get(i);
            String cache = caches.get(i);
            ResumeDetailVO resumeDetailVO;
            if (StringUtils.isNotBlank(cache)) {
                resumeDetailVO = JsonUtil.fromJson(cache, ResumeDetailVO.class);
            } else {
                resumeDetailVO = refreshResumeDetailCache(resumeId);
            }
            if (resumeDetailVO != null) {
                list.add(resumeDetailVO);
            }
        }
        return list;
    }

    /**
     * 查询简历详情
     *
     * @param resumeId
     * @return
     */
    public ResumeBaseVO getResumeBaseFromCache(String resumeId) {
        ResumeBaseVO resumeBaseVO = this.getResumeDetailFromCache(resumeId);
        Assert.isTrue(resumeBaseVO != null, "简历不存在或被删除");
        return resumeBaseVO;
    }

    public ResumeDetailVO getResumeDetailFromCache(String resumeId) {
        String key = RedisKeyEnum.JIANLI_BOT_RESUME_DETAIL_CACHE.getKey(resumeId);
        String cache = redisUtil.get(key);
        ResumeDetailVO resumeDetail = null;
        if (StringUtils.isNotBlank(cache)) {
            resumeDetail = JsonUtil.fromJson(cache, ResumeDetailVO.class);
        } else {
            resumeDetail = refreshResumeDetailCache(resumeId);
        }
        return resumeDetail;
    }
    
    /**
     *                        前端生成Pdf的接口
     * 保存简历信息
     * 此方法接收一个包含简历信息的请求对象，处理简历的保存逻辑，包括验证简历内容、解析和处理简历模板、上传封面图片、更新数据库和缓存
     *
     * @param onePaperSaveRequest 包含简历信息的请求对象，包括简历的HTML内容、封面图片、用户ID等
     * @return 返回保存后的简历ID
     * @author: CYM-pai
     * @date: 2025/05/06 15:26
     **/
    public String save(OnePaperSaveRequestVO onePaperSaveRequest) {
        // 获取简历ID
        String id = onePaperSaveRequest.getId();
        // 如果简历内容的HTML为空，则直接返回ID，表示无需进一步处理
        if (StringUtils.isBlank(onePaperSaveRequest.getContentHtml())) {
            return id;
        }
        // 获取简历封面和用户ID
        String cover = onePaperSaveRequest.getCover();
        String userId = onePaperSaveRequest.getUserId();
        
        // 通过简历ID、用户ID和删除状态查询简历实体，确保简历存在
        JianLiPaiResumeEntity jianLiPaiResumeEntity = jianLiBotResumeDAO.findAllByResumeIdAndCreateByAndDeleted(onePaperSaveRequest.getId(), userId, false);
        // 如果简历实体为空，抛出异常，提示简历不存在
        Assert.isTrue(jianLiPaiResumeEntity != null, "简历不存在！");
        
        // 初始化简历内容和详情对象
        String content = onePaperSaveRequest.getContent();
        ResumeDetail resumeDetail = null;
        try {
            // 根据简历模板代码获取相应的模板服务
            AbstractTemplateService templateService = ResumeTemplateFactory.getTemplateService(jianLiPaiResumeEntity.getTemplateCode());
            // 解析简历JSON内容为ResumeDetail对象
            resumeDetail = (ResumeDetail) templateService.parseJson(onePaperSaveRequest.getContentHtml());
            // 根据ResumeDetail对象生成简历内容
            //content = templateService.parseContent(resumeDetail);
        } catch (Exception e) {
            // 如果解析过程中发生异常，记录错误日志并返回简历ID
            log.error("简历自动保存失败，onePaperSaveRequest:{}", onePaperSaveRequest);
            return id;
        }
        // 将封面图片上传到OSS，并获取上传后的URL
        String coverUrl = uploadBase64Image2OSS(cover, onePaperSaveRequest.getId());
        
        // 如果封面URL不为空，更新简历实体的封面URL
        if (StringUtils.isNotBlank(coverUrl)) {
            jianLiPaiResumeEntity.setCover(coverUrl);
        }
        // 更新简历实体的HTML内容、JSON内容、文本内容和文件大小
        jianLiPaiResumeEntity.setResumeHtml(onePaperSaveRequest.getContentHtml());
        jianLiPaiResumeEntity.setResumeJson(JsonUtil.toJsonString(resumeDetail));
        jianLiPaiResumeEntity.setResumeContent(content);
        jianLiPaiResumeEntity.setFileSize(getStringSize(content));
        // 保存更新后的简历实体到数据库
        jianLiBotResumeDAO.save(jianLiPaiResumeEntity);
        // 刷新简历详情缓存
        refreshResumeDetailCache(id);
        // 返回保存后的简历ID
        return id;
    }
    
    /**
     * 将Base64格式的图片上传到阿里云OSS
     * 此方法尚未实现实际的上传逻辑
     *
     * @param base64   图片的Base64编码字符串
     * @param resumeId 简历ID，用于命名或分类图片
     * @return 返回上传后的图片URL或标识
     */
    private String uploadBase64Image2OSS(String base64, String resumeId) {
        return base64;
    }
    
    /**
     * 获取用户的聊天上下文信息
     * 从Redis缓存中读取并解析聊天信息列表
     *
     * @param userId 用户ID，用于获取对应的聊天上下文
     * @return 返回解析后的聊天信息列表，如果没有聊天记录则返回空列表
     */
    public List<ChatCompletionMessage> getContextMessage(String userId) {
        String key = RedisKeyEnum.ONE_PAPER_AI_CHAT_CONTEXT.getKey(userId);
        String cache = redisUtil.get(key);
        if (StringUtils.isBlank(cache)) {
            return new ArrayList<>();
        }
        return JsonUtil.fromJson(cache, new TypeReference<List<ChatCompletionMessage>>() {
        });
    }
    
    /**
     * 添加用户的聊天信息到上下文中
     * 将新的聊天信息添加到列表中，并更新Redis缓存
     *
     * @param userId  用户ID，用于获取对应的聊天上下文
     * @param message 新的聊天信息对象
     * @return 返回更新后的聊天信息列表
     */
    public List<ChatCompletionMessage> addContextMessage(String userId, ChatCompletionMessage message) {
        String key = RedisKeyEnum.ONE_PAPER_AI_CHAT_CONTEXT.getKey(userId);
        String cache = redisUtil.get(key);
        List<ChatCompletionMessage> messages;
        if (StringUtils.isBlank(cache)) {
            messages = new ArrayList<>();
        } else {
            // 解析缓存为ChatCompletionMessage列表
            messages = JsonUtil.fromJson(cache, new TypeReference<List<ChatCompletionMessage>>() {
            });
        }
        messages.add(message);
        redisUtil.set(key, JsonUtil.toJsonString(messages));
        redisUtil.expire(key, RedisKeyEnum.ONE_PAPER_AI_CHAT_CONTEXT.getExpireTime());
        return messages;
    }
    
    /**
     * 清除用户的聊天上下文信息
     * 从Redis缓存中移除对应的聊天信息
     *
     * @param userId 用户ID，用于清除对应的聊天上下文
     */
    public void clearContextMessage(String userId) {
        String key = RedisKeyEnum.ONE_PAPER_AI_CHAT_CONTEXT.getKey(userId);
        redisUtil.remove(key);
    }
    
    /**
     * 获取字符串的大小
     * 计算给定字符串在UTF-8编码下的大小，并以KB为单位返回
     *
     * @param str 待测量的字符串
     * @return 返回字符串的大小，单位为KB
     */
    private String getStringSize(String str) {
        double kb = sizeInKB(str);
        return kb == -1 ? "" : kb + "kb";
    }
    
    /**
     * 计算字符串在UTF-8编码下的大小
     *
     * @param str 待测量的字符串
     * @return 返回字符串的大小，单位为KB，精确到小数点后两位
     */
    public static double sizeInKB(String str) {
        if (StringUtils.isBlank(str)) {
            return 0;
        }
        try {
            byte[] bytes = str.getBytes("UTF-8");
            double val = bytes.length / 1024.0;
            BigDecimal bg = new BigDecimal(val);
            return bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        } catch (Exception e) {
            return 0;
        }
    }

}
